﻿using System;
using System.Collections.Generic;
using System.ComponentModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SceneEngine
{
    public class BaseObject : I3DComponent
    {
        private Model model;
        private string modelPath;

        private string groupPath;

        private List<Vector3> vertices;

        private string name;

        private Matrix[] bones;
        private Matrix world;

        private bool placeOnTerrain;

        protected Vector3 position;
        protected Quaternion rotation = Quaternion.Identity;
        protected Vector3 scale;

        protected BoundingBox originalBounds;
        protected BoundingBox bounds;

        public Matrix RotationMatrix;

        public event RenamedEventHandler Renamed;

        [ReadOnly(true)]
        public string ModelPath
        {
            get { return modelPath; }
            set { modelPath = value; }
        }

        [CategoryAttribute("Transform"), DescriptionAttribute("Position of the object in the world")]
        public Vector3 Position
        {
            get { return position; }
            set 
            {
                position.X = (float)Math.Round(value.X, 3, MidpointRounding.AwayFromZero);
                position.Y = (float)Math.Round(value.Y, 3, MidpointRounding.AwayFromZero);
                position.Z = (float)Math.Round(value.Z, 3, MidpointRounding.AwayFromZero);

                if (placeOnTerrain && EngineGlobals.Scene.HeightMap != null)
                {
                    position.Y = (float)Math.Round(EngineGlobals.Scene.HeightMap.GetGroundHeight(position), 
                        3, MidpointRounding.AwayFromZero); 
                }

                UpdateWorld();
                SetUpBounds();
            }
        }
        [CategoryAttribute("Transform"), DescriptionAttribute("Rotation of the object"), ReadOnly(true)]
        public Quaternion Rotation
        {
            get { return rotation; }
            set
            {
                rotation = value;

                UpdateWorld();
                SetUpBounds();
            }
        }
        [CategoryAttribute("Transform"), DescriptionAttribute("Scale of the object")]
        public Vector3 Scale
        {
            get { return scale; }
            set
            {
                scale = value;
                UpdateWorld();
                SetUpBounds();
            }
        }

        [Browsable(false)]
        public BoundingBox Bounds
        {
            get { return bounds; }
        }
        [Browsable(false)]
        public Model Model
        {
            get { return model; }
        }

        [Browsable(false)]
        public string GroupPath
        {
            get { return groupPath; }
            set { groupPath = value; }
        }

        [Browsable(false)]
        public List<Vector3> Vertices
        {
            get { return vertices; }
            set { vertices = value; }
        }

        [Browsable(false)]
        public Matrix World
        {
            get { return world; }
        }

        [CategoryAttribute("Base"), DescriptionAttribute("The Name of the Object")]
        public string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    if (EngineGlobals.Scene.Contains(value))
                    {
                        System.Windows.Forms.MessageBox.Show("The scene already contains an object with this name, please enter another name");
                    }
                    else
                    {
                        OnRenamed(new RenamedEventArgs(name, value));
                        name = value;
                    }
                }
            }
        }

        [CategoryAttribute("Terrain"), DescriptionAttribute(
         "If true, the object will automatically adjust " + 
         "it's height so it is on top of the terrain")]
        public bool PlaceOnTerrain
        {
            get { return placeOnTerrain; }
            set 
            { 
                placeOnTerrain = value;

                if (placeOnTerrain && EngineGlobals.Scene.HeightMap != null)
                {
                    position.Y = (float)Math.Round(EngineGlobals.Scene.HeightMap.GetGroundHeight(position),
                        3, MidpointRounding.AwayFromZero);
                }

                UpdateWorld();
                SetUpBounds();
            }
        }

        public BaseObject(string name, Model model)
        {
            this.model = model;
            this.name = name;

            ModelInformation modelInfo = (ModelInformation)model.Tag;
            this.vertices = modelInfo.Vertices;

            this.scale = Vector3.One;

            this.bones = new Matrix[this.model.Bones.Count];
            this.model.CopyAbsoluteBoneTransformsTo(bones);

            UpdateWorld();
            GenerateBounds();
        }

        public BaseObject(string name, Model model, Vector3 position, Quaternion rotation, Vector3 scale)
            : this(name, model)
        {
            this.position.X = (float)Math.Round(position.X, 3, MidpointRounding.AwayFromZero);
            this.position.Y = (float)Math.Round(position.Y, 3, MidpointRounding.AwayFromZero);
            this.position.Z = (float)Math.Round(position.Z, 3, MidpointRounding.AwayFromZero);

            this.rotation = rotation;
            this.scale = scale;

            UpdateWorld();
            SetUpBounds();
        }

        private void OnRenamed(RenamedEventArgs e)
        {
            if (Renamed != null)
                Renamed(this, e);
        }

        protected void UpdateWorld()
        {
            world = Matrix.CreateScale(scale) * 
                    Matrix.CreateFromQuaternion(rotation) * 
                    Matrix.CreateTranslation(position);
        }

        public override string ToString()
        {
            return name;
        }

        public virtual void GenerateBounds()
        {
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            foreach (Vector3 corner in Vertices)
            {
                Vector3 worldCorner = Vector3.Transform(corner, Matrix.Identity);

                if (worldCorner.X < min.X) min.X = worldCorner.X;
                if (worldCorner.X > max.X) max.X = worldCorner.X;
                if (worldCorner.Y < min.Y) min.Y = worldCorner.Y;
                if (worldCorner.Y > max.Y) max.Y = worldCorner.Y;
                if (worldCorner.Z < min.Z) min.Z = worldCorner.Z;
                if (worldCorner.Z > max.Z) max.Z = worldCorner.Z;
            }

            originalBounds = new BoundingBox(min, max);
            SetUpBounds();
        }
        public virtual void SetUpBounds()
        {
            Vector3[] corners = originalBounds.GetCorners();

            for (int i = 0; i < corners.Length; i++)
            {
                corners[i] = Vector3.Transform(corners[i], world);
            }

            bounds = BoundingBox.CreateFromPoints(corners);
        }

        /// <summary>
        /// Determines if the base object was hit by the ray.
        /// </summary>
        public virtual float? Intersects(Ray pickRay)
        {
            return CollisionHelper.RayIntersectsModel(vertices.ToArray(), bounds, world, pickRay);
        }

        /// <summary>
        /// Moves the object and it's bounds by the specified amount.
        /// </summary>
        /// <param name="amount"></param>
        public void Move(Vector3 amount)
        {
            if (placeOnTerrain && EngineGlobals.Scene.HeightMap != null)
            {
                float newHeight = EngineGlobals.Scene.HeightMap.GetGroundHeight(position);
                float difference = newHeight - position.Y;

                amount.Y = difference;
            }

            position += amount;
            bounds.Max += amount;
            bounds.Min += amount;

            position.X = (float)Math.Round(position.X, 3, MidpointRounding.AwayFromZero);
            position.Y = (float)Math.Round(position.Y, 3, MidpointRounding.AwayFromZero);
            position.Z = (float)Math.Round(position.Z, 3, MidpointRounding.AwayFromZero);

            UpdateWorld();
        }
        /// <summary>
        /// Rotates the object and it's bounds by the specified amount.
        /// </summary>
        public void Rotate(Quaternion amount, bool localRotation)
        {
            if (localRotation)
            {
                rotation *= amount;
            }
            else
            {
                rotation = amount * rotation;
            }

            UpdateWorld();
        }

        public virtual void Update()
        {

        }
    }
}
